package net;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.Arrays;

import util.Logger;

public class ClientSideConnection extends Connection {

	protected DatagramSocket udpSocket;

	public ClientSideConnection(ServerInfo info) {
		this(info.ip, info.port);
	}

	public ClientSideConnection(String ip, int tcpPort) {
		this.ip = ip;
		this.tcpPort = tcpPort;
	}

	public void connect() throws IOException {
		tcpSocket = new Socket(ip, tcpPort);
		open();
		tcpThread = new Thread(this);
		tcpThread.start();
	}

	@Override
	protected void intercept(byte[] bytes) {
		ByteReader reader = new ByteReader(bytes);
		reader.read();
		if (reader.read() == OPEN_UDP) {
			if (udpSocket != null) {
				Logger.log(Logger.WARNING,
						"Attempt to open UDP socket while already open.");
				return;
			}
			int port = reader.readInt();
			send(UDP_PORT + DELIM + port);
			startUDP(port);
		}
		/*
		 * String msg = new String(bytes); String[] m = msg.split(DELIM); String
		 * command = m[1]; if (command.equals(OPEN_UDP)) { if (udpSocket !=
		 * null) { Logger.log(Logger.WARNING,
		 * "Attempt to open UDP socket while already open."); return; } int port
		 * = Integer.valueOf(m[2]); startUDP(port); }
		 */
	}

	private final DatagramPacket packet = new DatagramPacket(new byte[0], 0);

	@Override
	public synchronized void sendUDP(byte[] bytes, int offset, int len) {
		if (udpSocket == null) {
			Logger.log(Logger.WARNING,
					"Cannot send UDP while no socket is open.");
			return;
		}
		packet.setData(bytes, offset, len);
		packet.setAddress(tcpSocket.getInetAddress());
		packet.setPort(udpOutbound);
		try {
			udpSocket.send(packet);
		} catch (Exception e) {
			Logger.log(e);
		}
	}

	protected void startUDP(int outPort) {
		if (udpPacketListener != null) {
			Logger.log(Logger.WARNING, "UDP port is already open!");
			return;
		}
		udpOutbound = outPort;
		// int port = getPossiblePort();
		// Logger.log(Logger.FINEST, "attempting to bind udp: " + port);
		try {
			Logger.log(Logger.FINE, "Server has requested"
					+ " a UDP connection on port: " + outPort);
			udpSocket = new DatagramSocket();
			udpPacketListener = new Thread(udpListener);
			udpPacketListener.start();
			Logger.log(Logger.FINE, "UDP Socket opened on port "
					+ udpSocket.getLocalPort());
			sendUDP(new byte[] { OPEN_UDP });
			Logger.log(Logger.FINE, "UDP greeting sent to server.");
			// we want to notify the server of the port we bound to
			// send(OPEN_UDP + DELIM + port);
		} catch (SocketException e) {
			Logger.log(Logger.WARNING,
					"There was an error binding the udpSocket.");
			Logger.log(e);
		}
	}

	private final Runnable udpListener = new Runnable() {
		final byte[] buff = new byte[256];

		public void run() {
			DatagramPacket packet = new DatagramPacket(buff, buff.length);
			while (udpPacketListener != null) {
				try {
					udpSocket.receive(packet);
					byte[] copy = Arrays.copyOfRange(packet.getData(), packet
							.getOffset(), packet.getOffset()
							+ packet.getLength());
					udpReceived(copy);
				} catch (IOException e) {
					if (udpPacketListener != null) {
						Logger.log(e);
					}
					break;
				}
			}
		}
	};

	@Override
	public synchronized void exit() {
		try {
			udpPacketListener = null;
			if (udpSocket != null) {
				udpSocket.close();
				udpSocket = null;
			}
		} catch (Exception e) {
			Logger.log(e);
		}
		super.exit();
	}

	public void setInfo(String ip, int tcpPort) {
		this.ip = ip;
		this.tcpPort = tcpPort;
	}

}
